MTPO Milli Hack

Description: Add granularity to the millisecond timer in MTPO reported at the end of fights.

History:

v1.0 (5/1/20): Initial ver, updated end of fights for all characters except Tyson.
v2.0 (5/3/20): Update to add time to Tyson victory screen.
v3.0 (5/7/20): Replaced points counter with realtime last-punch-landed timer. It should not affect game timing/lag. 
I have seen instances of overrun ahead of actual round timer, this only appears to occur on knockdowns not ending the fight.

=====================================================================================

RAM position 0x306 contains the MS counter, while 0x307 appears to contain an inverse counter. It's not clear the role of 0x307, perhaps an anti-cheat/sanity check (not used afaict).
At the beginning of round 1, 0x308/9 are loaded with: 04 F9 (1st rd), 05 6D (2nd rd), 05 F8 (3rd rd). The MS counter at 0x306 adds 0x308 to itself with each iteration, until it is >= 0x64 (100).

This code can be traced:

 03:8780:AD 07 03  LDA $0307 = #$AF
 03:8783:18        CLC
 03:8784:6D 09 03  ADC $0309 = #$F9
 03:8787:8D 07 03  STA $0307 = #$AF
 03:878A:AD 06 03  LDA $0306 = #$5E		; MS
 03:878D:6D 08 03  ADC $0308 = #$04
 03:8790:8D 06 03  STA $0306 = #$5E
 03:8793:C9 64     CMP #$64			; is MS >= 0x64
 03:8795:90 52     BCC $87E9			; if not, skip updating time
 03:8797:E9 64     SBC #$64			; subtract 0x64
 03:8799:8D 06 03  STA $0306 = #$5E		; save the overflow
 
Once victory occurs, the following code translates the MS on the win screen:

 06:BC99:AD 06 03  LDA $0306 = #$14		; load MS
 06:BC9C:29 F0     AND #$F0			; clear low nibble
 06:BC9E:4A        LSR				; shift right x1
 06:BC9F:4A        LSR				; ""
 06:BCA0:4A        LSR				; ""
 06:BCA1:A8        TAY
 
So the above essentially rounds the actual MS to the next lowest 16, then divides by 8. This basically gives us all the even #s from 0-12.
Finally it takes this even # and pulls the limited MS values from a lookup table (there are a few versions):

 06:BCA2:B9 B0 BC  LDA $BCB0,Y @ $BD3D = #$5C
 06:BCA5:8D 07 20  STA PPU_DATA = #$00
 06:BCA8:C8        INY
 06:BCA9:B9 B0 BC  LDA $BCB0,Y @ $BD3D = #$5C
 06:BCAC:8D 07 20  STA PPU_DATA = #$00

BCB0: 01 01 03 06 05 09 07 02 09 03 0A 08 0A 0A  (if you subtract one from each, this is .00, .25, etc)

So we replace the drawing code and lookup table with a routine to convert the hexadecimal MS (0x306) directly to decimal (+1 to adjust for tile ID):

 06:BC99:AD 06 03  LDA $0306 = #$4A
 06:BC9C:C9 64     CMP #$64			; is the MS actually = 100? it can happen (race condition?) before the seconds are updated
 06:BC9E:90 02     BCC $BCA2
 06:BCA0:A9 63     LDA #$63			; if it is, we will just make it .99
 06:BCA2:A2 01     LDX #$01				
 06:BCA4:C9 0A     CMP #$0A				
 06:BCA6:90 06     BCC $BCAE			
 06:BCA8:E9 0A     SBC #$0A
 06:BCAA:E8        INX					
 06:BCAB:4C A4 BC  JMP $BCA4
 06:BCAE:A8        TAY
 06:BCAF:C8        INY
 06:BCB0:8E 07 20  STX PPU_DATA = #$00		; store tenths
 06:BCB3:8C 07 20  STY PPU_DATA = #$00		; store hundreths
 06:BCB6:60        RTS

*** v2.0: Disassembly for Tyson time addition ***

First thing to do is modify the victory text(0x120F7) to make room for the time. I simply truncated it at the 3rd line and added 'TIME' to start 4th line.

The Tyson victory screen code can be found at:

 06:B757: 20 66 B6  JSR $B666
 06:B75A: 20 5C AA  JSR $AA5C
 06:B75D: A9 FF     LDA #$FF
 06:B75F: 20 AE BF  JSR $BFAE
 06:B762: 20 B2 BF  JSR $BFB2
 06:B765: A9 06     LDA #$06
 06:B767: A2 05     LDX #$05
 06:B769: 20 21 BF  JSR $BF21			; this fills the PPU with the victory pic/text
 06:B76C: A9 01     LDA #$01			; replace with
 06:B76E: 20 13 C1  JSR $C113			; jmp to $8d50
 06:B771: A9 00     LDA #$00
 06:B773: A2 01     LDX #$01
 06:B775: 20 0D BF  JSR $BF0D

So we intercept @ B76C and jump to our code in some unused ROM space:

 04:8D50: A9 23     LDA #$23
 04:8D52: 8D 06 20  STA PPU_ADDRESS = #$44
 04:8D55: A9 56     LDA #$56
 04:8D57: 8D 06 20  STA PPU_ADDRESS = #$44	; set PPU write address to 2356, 4th line of text
 04:8D5A: 20 F2 C0  JSR $C0F2				; dump m:ss
 04:8D5D: 20 94 BC  JSR $BC94				; dump .ms
 04:8D60: A9 2B     LDA #$2B
 04:8D62: 8D 07 20  STA PPU_DATA = #$00		; ,
 04:8D65: A9 1C     LDA #$1C
 04:8D67: 8D 07 20  STA PPU_DATA = #$00		; R
 04:8D6A: A6 06     LDX $06 = #$01
 04:8D6C: E8        INX
 04:8D6D: 8E 07 20  STX PPU_DATA = #$00		; x
 04:8D70: A9 01     LDA #$01			; execute code
 04:8D72: 20 13 C1  JSR $C113			; we overwrote above
 04:8D75: 4C 71 B7  JMP $B771			; return to original code

*************************************************

*** v3.0: Disassembly for pts => time hack ******

First change the 'POINTS' text in the ROM (0x191D2) to 'TIME:'. Next, the code for updating the points total occurs @ C0C3:

 07:C0C3: A9 20     LDA #$20
 07:C0C5: 8D 06 20  STA PPU_ADDRESS = #$00
 07:C0C8: A9 88     LDA #$88			; $2088 is where pts total starts
 07:C0CA: 8D 06 20  STA PPU_ADDRESS = #$00
 07:C0CD: A2 00     LDX #$00
 07:C0CF: BD F1 03  LDA $03F1,X @ $0430 = #$00
 07:C0D2: 8D 07 20  STA PPU_DATA = #$00
 07:C0D5: E8        INX
 07:C0D6: E0 06     CPX #$06			; loop to print up to 6 digits
 07:C0D8: D0 F5     BNE $C0CF

The updated code for the time hack:

 07:C0C3: A9 20     LDA #$20
 07:C0C5: 8D 06 20  STA PPU_ADDRESS = #$4D
 07:C0C8: A9 87     LDA #$87				; adjusted start by -1 for more room
 07:C0CA: 8D 06 20  STA PPU_ADDRESS = #$4D
 07:C0CD: 20 F2 C0  JSR $C0F2				; dump m:ss
 07:C0D0: 20 94 BC  JSR $BC94				; dump .ms
 07:C0D3: EA        NOP
 07:C0D4: EA        NOP
 07:C0D5: EA        NOP
 07:C0D6: EA        NOP
 07:C0D7: EA        NOP
 07:C0D8: EA        NOP
 07:C0D9: EA        NOP

*************************************************

Tested on all characters in FCEUX 2.2.3 / Mesen 0.9.9.
Thanks to http://tasvideos.org/GameResources/NES/MikeTysonsPunchout/RamMap.html for helping get me started. Patch data below.

Stag Shot
==================================================================================
Source ROM: Mike Tyson's Punch-Out!! (USA).nes (md5=B9A66B2760DAA7D5639CBAD903DE8A18)

Comparing files mtpo.nes and MTPO-MILLI_V3.NES
000120F7: 1D 25
000120FC: 10 FF
000120FD: 13 FF
000120FE: 18 1E
000120FF: 11 13
00012100: 0F 17
00012101: 1C 0F
00012103: 1D FF
00012104: 1A FF
00012105: 0F FF
00012106: 0F FF
00012107: 0E FF
0001210A: 0C FF
0001210B: 0F FF
0001210C: 10 FF
0001210D: 19 FF
0001211A: 1F 25
0001211B: 0D 25
0001211C: 12 2A
00012120: 1C FF
00012121: 0F FF
00012122: 25 FF
00012123: 2A FF
00012D60: 00 A9
00012D61: 00 23
00012D62: 00 8D
00012D63: 00 06
00012D64: 00 20
00012D65: 00 A9
00012D66: 00 56
00012D67: 00 8D
00012D68: 00 06
00012D69: 00 20
00012D6A: 00 20
00012D6B: 00 F2
00012D6C: 00 C0
00012D6D: 00 20
00012D6E: 00 94
00012D6F: 00 BC
00012D70: 00 A9
00012D71: 00 2B
00012D72: 00 8D
00012D73: 00 07
00012D74: 00 20
00012D75: 00 A9
00012D76: 00 1C
00012D77: 00 8D
00012D78: 00 07
00012D79: 00 20
00012D7A: 00 A6
00012D7B: 00 06
00012D7C: 00 E8
00012D7D: 00 8E
00012D7E: 00 07
00012D7F: 00 20
00012D80: 00 A9
00012D81: 00 01
00012D82: 00 20
00012D83: 00 13
00012D84: 00 C1
00012D85: 00 4C
00012D86: 00 71
00012D87: 00 B7
000191D2: 1A 1E
000191D3: 19 13
000191D4: 13 17
000191D5: 18 0F
000191D6: 1E 2C
000191D7: 1D FF
000191D8: 2C FF
0001B77C: A9 4C
0001B77D: 01 50
0001B77E: 20 8D
0001B77F: 13 EA
0001B780: C1 EA
0001BCAC: 29 C9
0001BCAD: F0 64
0001BCAE: 4A 90
0001BCAF: 4A 02
0001BCB0: 4A A9
0001BCB1: A8 63
0001BCB2: B9 A2
0001BCB3: B0 01
0001BCB4: BC C9
0001BCB5: 8D 0A
0001BCB6: 07 90
0001BCB7: 20 06
0001BCB8: C8 E9
0001BCB9: B9 0A
0001BCBA: B0 E8
0001BCBB: BC 4C
0001BCBC: 8D A4
0001BCBD: 07 BC
0001BCBE: 20 A8
0001BCBF: 60 C8
0001BCC0: 01 8E
0001BCC1: 01 07
0001BCC2: 03 20
0001BCC3: 06 8C
0001BCC4: 05 07
0001BCC5: 09 20
0001BCC6: 07 60
0001C0D9: 88 87
0001C0DD: A2 20
0001C0DE: 00 F2
0001C0DF: BD C0
0001C0E0: F1 20
0001C0E1: 03 94
0001C0E2: 8D BC
0001C0E3: 07 EA
0001C0E4: 20 EA
0001C0E5: E8 EA
0001C0E6: E0 EA
0001C0E7: 06 EA
0001C0E8: D0 EA
0001C0E9: F5 EA

Patched ROM v3 md5=1E613757E0CA9FE41A637913FD8CA3AA